﻿using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Xml.Linq;
using Engine;
using XmlUtilities;

namespace Game
{
    public static class XCraftingRecipesManager
    {
        public static List<CraftingRecipe> m_recipes = new List<CraftingRecipe>();

        public static ReadOnlyList<CraftingRecipe> Recipes => new ReadOnlyList<CraftingRecipe>(m_recipes);

        public static void Initialize()
        {
            foreach (XElement item in ContentManager.Get<XElement>("FCCraftingRecipes").Descendants("Recipe"))
            {
                CraftingRecipe craftingRecipe = new CraftingRecipe();
                craftingRecipe.Ingredients = new string[16];
                string attributeValue = XmlUtils.GetAttributeValue<string>(item, "Result");
                string text = XmlUtils.GetAttributeValue<string>(item, "Description");
                if (text.StartsWith("[") && text.EndsWith("]") && LanguageControl.TryGetBlock(attributeValue, "CRDescription:" + text.Substring(1, text.Length - 2), out var result))
                {
                    text = result;
                }

                craftingRecipe.ResultValue = DecodeResult(attributeValue);
                craftingRecipe.ResultCount = XmlUtils.GetAttributeValue<int>(item, "ResultCount");
                string attributeValue2 = XmlUtils.GetAttributeValue(item, "Remains", string.Empty);
                if (!string.IsNullOrEmpty(attributeValue2))
                {
                    craftingRecipe.RemainsValue = DecodeResult(attributeValue2);
                    craftingRecipe.RemainsCount = XmlUtils.GetAttributeValue<int>(item, "RemainsCount");
                }

                craftingRecipe.RequiredHeatLevel = XmlUtils.GetAttributeValue<float>(item, "RequiredHeatLevel");
                craftingRecipe.RequiredPlayerLevel = XmlUtils.GetAttributeValue(item, "RequiredPlayerLevel", 1f);
                craftingRecipe.Description = text;
                craftingRecipe.Message = XmlUtils.GetAttributeValue<string>(item, "Message", null);
                if (craftingRecipe.ResultCount > BlocksManager.Blocks[Terrain.ExtractContents(craftingRecipe.ResultValue)].GetMaxStacking(craftingRecipe.ResultValue))
                {
                    throw new InvalidOperationException("In recipe for \"" + attributeValue + "\" ResultCount is larger than max stacking of result block.");
                }

                if (craftingRecipe.RemainsValue != 0 && craftingRecipe.RemainsCount > BlocksManager.Blocks[Terrain.ExtractContents(craftingRecipe.RemainsValue)].GetMaxStacking(craftingRecipe.RemainsValue))
                {
                    throw new InvalidOperationException("In Recipe for \"" + attributeValue2 + "\" RemainsCount is larger than max stacking of remains block.");
                }

                Dictionary<char, string> dictionary = new Dictionary<char, string>();
                foreach (XAttribute item2 in from a in item.Attributes()
                                             where a.Name.LocalName.Length == 1 && char.IsLower(a.Name.LocalName[0])
                                             select a)
                {
                    DecodeIngredient(item2.Value, out var craftingId, out var data);
                    if (BlocksManager.FindBlocksByCraftingId(craftingId).Length == 0)
                    {
                        throw new InvalidOperationException("Block with craftingId \"" + item2.Value + "\" not found.");
                    }

                    if (data.HasValue && (data.Value < 0 || data.Value > 262143))
                    {
                        throw new InvalidOperationException("Data in recipe ingredient \"" + item2.Value + "\" must be between 0 and 0x3FFFF.");
                    }

                    dictionary.Add(item2.Name.LocalName[0], item2.Value);
                }

                string[] array = item.Value.Trim().Split(new string[1] { "\n" }, StringSplitOptions.None);
                for (int i = 0; i < array.Length; i++)
                {
                    int num = array[i].IndexOf('"');
                    int num2 = array[i].LastIndexOf('"');
                    if (num < 0 || num2 < 0 || num2 <= num)
                    {
                        throw new InvalidOperationException("Invalid recipe line.");
                    }

                    string text2 = array[i].Substring(num + 1, num2 - num - 1);
                    for (int j = 0; j < text2.Length; j++)
                    {
                        char c = text2[j];
                        if (char.IsLower(c))
                        {
                            string text3 = dictionary[c];
                            craftingRecipe.Ingredients[j + i * 4] = text3;
                        }
                    }
                }

                m_recipes.Add(craftingRecipe);
                m_recipes.Sort(delegate (CraftingRecipe r1, CraftingRecipe r2)
                {
                    int y = r1.Ingredients.Count((string s) => !string.IsNullOrEmpty(s));
                    int x = r2.Ingredients.Count((string s) => !string.IsNullOrEmpty(s));
                    return Comparer<int>.Default.Compare(x, y);
                });
            }
        }

        public static CraftingRecipe FindMatchingRecipe(SubsystemTerrain terrain, string[] ingredients, float heatLevel, ComponentPlayer componentPlayer)
        {
            float num = componentPlayer?.PlayerData.Level ?? 1f;
            CraftingRecipe craftingRecipe = null;
            Block[] blocks = BlocksManager.Blocks;
            for (int i = 0; i < blocks.Length; i++)
            {
                CraftingRecipe adHocCraftingRecipe = blocks[i].GetAdHocCraftingRecipe(terrain, ingredients, heatLevel, componentPlayer);
                if (adHocCraftingRecipe != null && MatchRecipe(adHocCraftingRecipe.Ingredients, ingredients))
                {
                    craftingRecipe = adHocCraftingRecipe;
                    break;
                }
            }

            if (craftingRecipe != null && heatLevel < craftingRecipe.RequiredHeatLevel)
            {
                craftingRecipe = null;
            }

            if (craftingRecipe == null)
            {
                foreach (CraftingRecipe recipe in Recipes)
                {
                    if (MatchRecipe(recipe.Ingredients, ingredients))
                    {
                        craftingRecipe = recipe;
                        if (heatLevel < craftingRecipe.RequiredHeatLevel)
                        {
                            craftingRecipe = null;
                        }

                        break;
                    }
                }
            }

            return craftingRecipe;
        }

        public static bool MatchRecipe(string[] requiredIngredients, string[] actualIngredient)
        {
            if (requiredIngredients.Length <= 9 || actualIngredient.Length <= 9)
            {
                return BaseMatchRecipe(requiredIngredients, actualIngredient);
            }

            string[] array = new string[16];
            for (int i = 0; i < 2; i++)
            {
                for (int j = -4; j <= 4; j++)
                {
                    for (int k = -4; k <= 4; k++)
                    {
                        bool flip = ((i != 0) ? true : false);
                        if (!TransformRecipe(array, requiredIngredients, k, j, flip))
                        {
                            continue;
                        }

                        bool flag = true;
                        for (int l = 0; l < 16; l++)
                        {
                            if (l == actualIngredient.Length || !CompareIngredients(array[l], actualIngredient[l]))
                            {
                                flag = false;
                                break;
                            }
                        }

                        if (flag)
                        {
                            return true;
                        }
                    }
                }
            }

            return false;
        }

        public static bool BaseMatchRecipe(string[] requiredIngredients, string[] actualIngredients)
        {
            string[] array = new string[9];
            for (int i = 0; i < 2; i++)
            {
                for (int j = -3; j <= 3; j++)
                {
                    for (int k = -3; k <= 3; k++)
                    {
                        bool flip = ((i != 0) ? true : false);
                        if (!TransformRecipe(array, requiredIngredients, k, j, flip))
                        {
                            continue;
                        }

                        bool flag = true;
                        for (int l = 0; l < 9; l++)
                        {
                            if (l == actualIngredients.Length || !CompareIngredients(array[l], actualIngredients[l]))
                            {
                                flag = false;
                                break;
                            }
                        }

                        if (flag)
                        {
                            return true;
                        }
                    }
                }
            }

            return false;
        }

        public static int DecodeResult(string result)
        {
            string[] array = result.Split(':');
            Block block = BlocksManager.FindBlockByTypeName(array[0], throwIfNotFound: true);
            int data = ((array.Length >= 2) ? int.Parse(array[1], CultureInfo.InvariantCulture) : 0);
            return Terrain.MakeBlockValue(block.BlockIndex, 0, data);
        }

        public static void DecodeIngredient(string ingredient, out string craftingId, out int? data)
        {
            string[] array = ingredient.Split(':');
            craftingId = array[0];
            data = ((array.Length >= 2) ? new int?(int.Parse(array[1], CultureInfo.InvariantCulture)) : null);
        }

        public static bool TransformRecipe(string[] transformedIngredients, string[] ingredients, int shiftX, int shiftY, bool flip)
        {
            for (int i = 0; i < 16; i++)
            {
                transformedIngredients[i] = null;
            }

            for (int j = 0; j < 4; j++)
            {
                for (int k = 0; k < 4; k++)
                {
                    int num = (flip ? (4 - k - 1) : k) + shiftX;
                    int num2 = j + shiftY;
                    string text = ingredients[k + j * 4];
                    if (num >= 0 && num2 >= 0 && num < 4 && num2 < 4)
                    {
                        transformedIngredients[num + num2 * 4] = text;
                    }
                    else if (!string.IsNullOrEmpty(text))
                    {
                        return false;
                    }
                }
            }

            return true;
        }

        private static bool CompareIngredients(string requiredIngredient, string actualIngredient)
        {
            if (requiredIngredient == null)
            {
                return actualIngredient == null;
            }

            if (actualIngredient == null)
            {
                return requiredIngredient == null;
            }

            DecodeIngredient(requiredIngredient, out var craftingId, out var data);
            DecodeIngredient(actualIngredient, out var craftingId2, out var data2);
            if (!data2.HasValue)
            {
                throw new InvalidOperationException("Actual ingredient data not specified.");
            }

            if (craftingId == craftingId2)
            {
                if (!data.HasValue)
                {
                    return true;
                }

                return data.Value == data2.Value;
            }

            return false;
        }
    }
}